﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

namespace Jayden.Dll.ECMAScript
{
	public class GrammarParser
	{
		#region String Contants
		public const string NullLiteral = "null";
		public const string TrueLiteral = "true";
		public const string FalseLiteral = "false";
		public const string UndefinedLiteral = "undefined";
		public const string This = "this";
		public static readonly List<string> Keywords = new List<string>(new string[]
		{
			"break",
			"case",
			"catch",
			"continue",
			"debugger",
			"default",
			"delete",
			"do",
			"else",
			"finally",
			"for",
			"function",
			"if",
			"in",
			"instanceof",
			"new",
			"return",
			"switch",
			This,
			"throw",
			"try",
			"typeof",
			"var",
			"void",
			"while",
			"with"
		});
		public static readonly List<string> FutureReservedWords = new List<string>(new string[]
		{
			"class",
			"const",
			"enum",
			"export",
			"extends",
			"import",
			"super"
		});
		public static readonly List<string> FutureReservedWordsStrictMode = new List<string>(new string[]
		{
			"implements",
			"interface",
			"let",
			"package",
			"private",
			"protected",
			"public",
			"static",
			"yield"
		});
		public static readonly List<string> AssignmentOperators = new List<string>(new string[]
		{
			"+=",
			"-=",
			"*=",
			"%=",
			"<<=",
			">>=",
			">>>=",
			"&=",
			"|=",
			"^="
		});
		public static readonly List<string> Punctuators = new List<string>(new string[]
		{
			"{", "}",
			"(", ")",
			"[", "]",
			".",
			";",
			",",
			"<",
			">",
			"<=",
			">=",
			"==",
			"!=",
			"===",
			"!==",
			"+",
			"-",
			"*",
			"%",
			"++",
			"--",
			"<<",
			">>",
			">>>",
			"&",
			"|",
			"^",
			"!",
			"~",
			"&&",
			"||",
			"?",
			":",
			"="
		});
		public static readonly List<string> DivPunctuators = new List<string>(new string[] { "/", "/=" });
		#endregion
		static GrammarParser()
		{
			Punctuators.AddRange(AssignmentOperators);
			Comparison<string> longestFirst = delegate(string a, string b)
			{
				int compare = -a.Length.CompareTo(b.Length);
				if (compare == 0)
					return a.CompareTo(b);
				return compare;
			};
			Keywords.Sort(longestFirst);
			FutureReservedWords.Sort(longestFirst);
			FutureReservedWordsStrictMode.Sort(longestFirst);
			Punctuators.Sort(longestFirst);
			DivPunctuators.Sort(longestFirst);
		}

		public static bool IsWhiteSpace(char c)
		{
			switch(c)
			{
				case '\u0009': // tab <TAB>
				case '\u000b': // vertical tab <VT>
				case '\u000c': // form feed <FF>
				case '\u0020': // space <SP>
				case '\u00a0': // no-break space <NBSP>
				case '\ufeff': // byte order mark <BOM>
					return true;
			}
			return char.IsWhiteSpace(c); // any othe unicode "space separator" <USP>
		}
		public static bool IsLineTerminator(char c)
		{
			switch(c)
			{
				case '\u000a': // line feed <LF>
				case '\u000d': // carriage return <CR>
				case '\u2028': // line separator <LS>
				case '\u2029': // paragraph separator <PS>
					return true;
			}
			return false;
		}
		public static bool IsHexDigit(char c)
		{
			return char.IsDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
		}
		public static bool IsCombiningMark(char c)
		{
			UnicodeCategory categ = char.GetUnicodeCategory(c);
			if (categ == UnicodeCategory.NonSpacingMark || categ == UnicodeCategory.SpacingCombiningMark)
				return true;
			return false;
		}
		public static bool IsConnectorPunctuation(char c)
		{
			UnicodeCategory categ = char.GetUnicodeCategory(c);
			if (categ == UnicodeCategory.ConnectorPunctuation)
				return true;
			return false;
		}
		public static bool IsSingleEscape(char c)
		{
			switch(c)
			{
				case '\'':
				case '"':
				case '\\':
				case 'b':
				case 'f':
				case 'n':
				case 'r':
				case 't':
				case 'v':
					return true;
			}
			return false;
		}
		public static bool IsEscape(char c)
		{
			if (IsSingleEscape(c) || c == 'u' || c == 'x' || char.IsDigit(c))
				return true;
			return false;
		}
		public static bool IsNonEscape(char c)
		{
			return !(IsEscape(c) || IsLineTerminator(c));
		}

		public static bool IsLineTerminatorSequence(string script, ref int index)
		{
			if (index < script.Length)
			{
				if (IsLineTerminator(script[index]))
				{
					if (script[index] == '\u000d' && index + 1 < script.Length && script[index + 1] == '\u000a')
						index++;
					index++;
					return true;
				}
			}
			return false;
		}
		public static bool IsComment(string script, ref int index)
		{
			if (IsMuliLineComment(script, ref index))
				return true;
			if (IsSingleLineComment(script, ref index))
				return true;
			return false;
		}
		public static bool IsMuliLineComment(string script, ref int index)
		{
			int copyIndex = index;
			if (copyIndex + 1 < script.Length && script[copyIndex] == '/' && script[copyIndex + 1] == '*')
			{
				copyIndex += 2;
				SkipMultiLineCommentChars(script, ref copyIndex);
				if (copyIndex + 1 < script.Length && script[copyIndex] == '*' && script[copyIndex + 1] == '/')
				{
					index = copyIndex + 2;
					return true;
				}
			}
			return false;
		}
		public static bool IsSingleLineComment(string script, ref int index)
		{
			if (index + 1 < script.Length && script[index] == '/' && script[index + 1] == '/')
			{
				index += 2;
				SkipSingleLineCommentChars(script, ref index);
				return true;
			}
			return false;
		}
		public static void SkipMultiLineCommentChars(string script, ref int index)
		{
			while (index + 1 < script.Length && !(script[index] == '*' && script[index + 1] == '/'))
				index++;
		}
		public static void SkipSingleLineCommentChars(string script, ref int index)
		{
			while (index < script.Length && !IsLineTerminator(script[index]))
				index++;
		}
		public static bool IsToken(string script, ref int index)
		{
			if (IsIdentifierName(script, ref index))
				return true;
			if (IsPunctuator(script, ref index))
				return true;
			if (IsNumericLiteral(script, ref index))
				return true;
			if (IsStringLiteral(script, ref index))
				return true;
			return false;
		}
		public static bool IsPunctuator(string script, ref int index)
		{
			for (int c = 0; c < Punctuators.Count; c++)
				if (StartsWithAt(script, index, Punctuators[c]))
				{
					index += Punctuators[c].Length;
					return true;
				}
			return false;
		}
		public static bool IsIdentifier(string script, ref int index)
		{
			int indexCopy = index;
			if (!IsReservedWord(script, ref indexCopy) && IsIdentifierName(script, ref index))
				return true;
			return false;
		}
		public static bool IsIdentifierName(string script, ref int index)
		{
			if (IsIdentifierStart(script, ref index))
			{
				while (IsIdentifierPart(script, ref index))
					;
				return true;
			}
			return false;
		}
		public static bool IsIdentifierStart(string script, ref int index)
		{
			if (index < script.Length && (script[index] == '_' || script[index] == '$' || char.IsLetter(script[index])))
			{
				index++;
				return true;
			}
			if (index < script.Length && script[index] == '\\')
			{
				int copyIndex = index + 1;
				if (IsUnicodeEscapeSequence(script, ref copyIndex))
				{
					index = copyIndex;
					return true;
				}
			}
			return false;
		}
		public static bool IsIdentifierPart(string script, ref int index)
		{
			if (IsIdentifierStart(script, ref index))
				return true;
			if (index < script.Length && (IsCombiningMark(script[index]) || IsConnectorPunctuation(script[index]) || char.IsDigit(script[index]) || script[index] == '\u200c' || script[index] == '\u200d'))
			{
				index++;
				return true;
			}
			return false;
		}
		public static bool IsUnicodeEscapeSequence(string script, ref int index)
		{
			if (index + 4 < script.Length && script[index] == 'u' && IsHexDigit(script[index + 1]) && IsHexDigit(script[index + 2]) && IsHexDigit(script[index + 3]) && IsHexDigit(script[index + 4]))
			{
				index += 5;
				return true;
			}
			return false;
		}
		public static bool IsHexEscapeSequence(string script, ref int index)
		{
			if (index + 2 < script.Length && script[index] == 'x' && IsHexDigit(script[index + 1]) && IsHexDigit(script[index + 2]))
			{
				index += 3;
				return true;
			}
			return false;
		}
		public static bool IsReservedWord(string script, ref int index)
		{
			if (IsKeyword(script, ref index))
				return true;
			if (IsFutureReservedWord(script, ref index))
				return true;
			if (IsNullLiteral(script, ref index))
				return true;
			if (IsBooleanLiteral(script, ref index))
				return true;
			return false;
		}
		public static bool IsKeyword(string script, ref int index)
		{
			for (int c = 0; c < Keywords.Count; c++)
				if (StartsWithAt(script, index, Keywords[c]))
				{
					index += Keywords[c].Length;
					return true;
				}
			return false;
		}
		public static bool IsFutureReservedWord(string script, ref int index)
		{
			for (int c = 0; c < FutureReservedWords.Count; c++)
				if (StartsWithAt(script, index, FutureReservedWords[c]))
				{
					index += FutureReservedWords[c].Length;
					return true;
				}
			for (int c = 0; c < FutureReservedWordsStrictMode.Count; c++)
				if (StartsWithAt(script, index, FutureReservedWordsStrictMode[c]))
				{
					index += FutureReservedWordsStrictMode[c].Length;
					return true;
				}
			return false;
		}
		public static bool IsDivPunctuator(string script, ref int index)
		{
			for (int c = 0; c < DivPunctuators.Count; c++)
				if (StartsWithAt(script, index, DivPunctuators[c]))
				{
					index += DivPunctuators[c].Length;
					return true;
				}
			return false;
		}
		public static bool IsNullLiteral(string script, ref int index)
		{
			if (StartsWithAt(script, index, NullLiteral))
			{
				index += NullLiteral.Length;
				return true;
			}
			return false;
		}
		public static bool IsBooleanLiteral(string script, ref int index)
		{
			if (StartsWithAt(script, index, TrueLiteral))
			{
				index += TrueLiteral.Length;
				return true;
			}
			if (StartsWithAt(script, index, FalseLiteral))
			{
				index += FalseLiteral.Length;
				return true;
			}
			return false;
		}
		public static bool IsLiteral(string script, ref int index)
		{
			if (IsNullLiteral(script, ref index))
				return true;
			if(IsBooleanLiteral(script, ref index))
				return true;
			if(IsNumericLiteral(script, ref index))
				return true;
			if(IsStringLiteral(script, ref index))
				return true;
			if (IsRegularExpressionLiteral(script, ref index))
				return true;
			return false;
		}
		public static bool IsNumericLiteral(string script, ref int index)
		{
			if (IsDecimalLiteral(script, ref index))
				return true;
			if (IsHexIntegerLiteral(script, ref index))
				return true;
			return false;
		}
		public static bool IsDecimalLiteral(string script, ref int index)
		{
			bool result = false;
			int indexCopy = index;
			if (IsDecimalIntegerLiteral(script, ref indexCopy))
				result = true;
			if (indexCopy < script.Length && script[indexCopy] == '.')
			{
				indexCopy++;
				while (indexCopy < script.Length && char.IsDigit(script[indexCopy]))
				{
					indexCopy++;
					result = true;
				}
			}
			if (result)
			{
				IsExponentPart(script, ref indexCopy);
				index = indexCopy;
			}
			return result;
		}
		public static bool IsDecimalIntegerLiteral(string script, ref int index)
		{
			if (index < script.Length && script[index] == '0')
			{
				index++;
				return true;
			}
			if (index < script.Length && char.IsDigit(script[index]))
			{
				while (index < script.Length && char.IsDigit(script[index]))
					index++;
				return true;
			}
			return false;
		}
		public static bool IsExponentPart(string script, ref int index)
		{
			if (index < script.Length && (script[index] == 'e' || script[index] == 'E'))
			{
				int indexCopy = index + 1;
				if (IsSignedInteger(script, ref indexCopy))
				{
					index = indexCopy;
					return true;
				}
			}
			return false;
		}
		public static bool IsSignedInteger(string script, ref int index)
		{
			
			if (index < script.Length)
			{
				int indexCopy = index;
				if (script[indexCopy] == '+' || script[indexCopy] == '-')
					indexCopy++;
				if (indexCopy < script.Length && char.IsDigit(script[indexCopy]) && script[indexCopy] != '0')
				{
					while (indexCopy < script.Length && char.IsDigit(script[indexCopy]))
						indexCopy++;
					index = indexCopy;
					return true;
				}
			}
			return false;
		}
		public static bool IsHexIntegerLiteral(string script, ref int index)
		{
			if (index + 2 < script.Length && script[index] == '0' && (script[index + 1] == 'x' && script[index + 1] == 'X') && IsHexDigit(script[index + 2]))
			{
				index += 3;
				while (index < script.Length && IsHexDigit(script[index]))
					index++;
				return true;
			}
			return false;
		}
		public static bool IsStringLiteral(string script, ref int index)
		{
			if (index < script.Length && (script[index] == '"' || script[index] == '\''))
			{
				char quoteChar = script[index];
				int indexCopy = index + 1;
				SkipStringCharacters(script, ref indexCopy, quoteChar);
				if (script[indexCopy] == quoteChar)
				{
					index = indexCopy + 1;
					return true;
				}
			}
			return false;
		}
		public static void SkipStringCharacters(string script, ref int index, char quoteChar)
		{
			while (index < script.Length && IsStringCharacter(script, ref index, quoteChar))
				;
		}
		public static bool IsStringCharacter(string script, ref int index, char quoteChar)
		{
			
			if (index < script.Length)
			{
				if (IsLineTerminator(script[index]) || script[index] == quoteChar)
					return false;
				int indexCopy = index;
				if(script[index] == '\\')
				{
					indexCopy++;
					if(IsEscapeSequence(script, ref indexCopy) || IsLineTerminatorSequence(script, ref index))
					{
						index = indexCopy;
						return true;
					}
					return false;
				}
				index++;
				return true;
			}
			return false;
		}
		public static bool IsEscapeSequence(string script, ref int index)
		{
			if (IsUnicodeEscapeSequence(script, ref index))
				return true;
			if (IsHexEscapeSequence(script, ref index))
				return true;
			if (index < script.Length)
			{
				if (script[index] == '0' && (index + 1 >= script.Length || !char.IsDigit(script[index + 1])))
				{
					index++;
					return true;
				}
				if (IsNonEscape(script[index]) || IsSingleEscape(script[index]))
				{
					index++;
					return true;
				}
			}
			return false;
		}
		public static bool IsRegularExpressionLiteral(string script, ref int index) { throw new NotImplementedException(); }
		public static bool IsInputElementDiv(string script, ref int index)
		{
			if (index < script.Length && (IsWhiteSpace(script[index]) || IsLineTerminator(script[index])))
			{
				index++;
				return true;
			}
			if(IsComment(script, ref index))
				return true;
			if(IsToken(script, ref index))
				return true;
			if(IsDivPunctuator(script, ref index))
				return true;
			return false;
		}
		public static bool IsInputElementRegExp(string script, ref int index)
		{
			if (index < script.Length && (IsWhiteSpace(script[index]) || IsLineTerminator(script[index])))
			{
				index++;
				return true;
			}
			if(IsComment(script, ref index))
				return true;
			if(IsToken(script, ref index))
				return true;
			if(IsRegularExpressionLiteral(script, ref index))
				return true;
			return false;
		}
		public static bool IsPrimaryExpression(string script, ref int index)
		{
			if (IsIdentifier(script, ref index))
				return true;
			if (IsLiteral(script, ref index))
				return true;
			if (IsArrayLiteral(script, ref index))
				return true;
			if (IsObjectLiteral(script, ref index))
				return true;
			if (index < script.Length)
			{
				if (StartsWithAt(script, index, This))
				{
					index += This.Length;
					return true;
				}
				if (script[index] == '(')
				{
					int indexCopy = index + 1;
					if (IsExpression(script, ref indexCopy))
					{
						if (indexCopy < script.Length && script[indexCopy] == ')')
						{
							index = indexCopy + 1;
							return true;
						}
					}
				}
			}
			return false;
		}
		public static bool IsArrayLiteral(string script, ref int index)
		{
			if (index < script.Length && script[index] == '[')
			{
				int indexCopy = index + 1;
				if (IsElemenList(script, ref indexCopy))
				{
					if (indexCopy < script.Length && script[indexCopy] == ',')
						indexCopy++;
				}
				SkipElision(script, ref indexCopy);
				if (indexCopy < script.Length && script[indexCopy] == ']')
				{
					index = indexCopy + 1;
					return true;
				}
			}
			return false;
		}
		public static bool IsElemenList(string script, ref int index)
		{
			int indexCopy = index;
			SkipElision(script, ref indexCopy);
			if (IsAssignmentExpression(script, ref indexCopy))
			{
				if (indexCopy < script.Length && script[indexCopy] == ',')
					IsElemenList(script, ref indexCopy);
				index = indexCopy;
				return true;
			}
			return false;
		}
		public static void SkipElision(string script, ref int index)
		{
			while (index < script.Length && script[index] == ',')
				index++;
		}
		
		public static bool IsAssignmentExpression(string script, ref int index)
		{
			if (IsConditionalExpression(script, ref index))
				return true;
			int indexCopy = index;
			if (IsLeftHandSideExpression(script, ref indexCopy))
			{
				if (indexCopy < script.Length && script[indexCopy] == '=')
					indexCopy++;
				else if (!IsAssignmentOperator(script, ref indexCopy))
					return false;
				if (IsAssignmentExpression(script, ref indexCopy))
				{
					index = indexCopy;
					return true;
				}
			}
			return false;
		}
		public static bool IsConditionalExpression(string script, ref int index)
		{
			if (IsLogicalOrExpression(script, ref index))
			{
				if (index < script.Length && script[index] == '?')
				{
					int indexCopy = index + 1;
					if (IsAssignmentExpression(script, ref indexCopy))
					{
						if (indexCopy < script.Length && script[indexCopy] == ':')
						{
							indexCopy++;
							if (IsAssignmentExpression(script, ref indexCopy))
							{
								index = indexCopy;
								return true;
							}
						}
					}
				}
				return true;
			}
			return false;
		}
		public static bool IsLogicalOrExpression(string script, ref int index)
		{
			if (IsLogicalAndExpression(script, ref index))
				return true;
			return false;
		}
		public static bool IsLogicalAndExpression(string script, ref int index) { throw new NotImplementedException(); }
		public static bool IsLeftHandSideExpression(string script, ref int index) { throw new NotImplementedException(); }
		public static bool IsAssignmentOperator(string script, ref int index)
		{
			for (int c = 0; c < AssignmentOperators.Count; c++)
				if (StartsWithAt(script, index, AssignmentOperators[c]))
				{
					index += AssignmentOperators[c].Length;
					return true;
				}
			return false;
		}
		public static bool IsObjectLiteral(string script, ref int index)
		{
			if (index < script.Length && script[index] == '{')
			{
				int indexCopy = index +1;
				if (IsPropertyNameAndValueList(script, ref indexCopy))
				{
					if (indexCopy < script.Length && script[indexCopy] == ',')
						indexCopy++;
				}
				if (indexCopy < script.Length && script[indexCopy] == '}')
				{
					index = indexCopy;
					return true;
				}
			}
			return false;
		}
		public static bool IsPropertyNameAndValueList(string script, ref int index) { throw new NotImplementedException(); }
		public static bool IsExpression(string script, ref int index)
		{
			int indexCopy = index;
			if (IsAssignmentExpression(script, ref indexCopy))
			{
				while (indexCopy < script.Length && script[indexCopy] == ',')
				{
					if (!IsAssignmentExpression(script, ref indexCopy))
						return false;
				}
				indexCopy = index;
				return true;
			}
			return false;
		}
		public static bool StartsWithAt(string script, int index, string otherScript)
		{
			if(script.Length - index < otherScript.Length)
				return false;
			for (int c = 0; c < otherScript.Length; c++)
				if (script[index + c] != otherScript[c])
					return false;
			return true;
		}
	}
}
